General Set UP

Library

library(dplyr)
library(readr)
library(broom)
library(ggplot2)
library(tidymodels) 
library(probably)
library(vip)
library(plotly)
library(ClusterR)
library(cluster)
library(vip)
tidymodels_prefer()
theme_set(theme_bw())       
Sys.setlocale("LC_TIME", "English")
## [1] "English_United States.1252"
set.seed(74)

Read in data

breastCa<-read_csv(file = "breast-cancer.csv")

Regression (Least Square & LASSO)

Data cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(radius_mean:fractal_dimension_mean) 

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-8)

Creation of cv folds

breastCa_Re_CV<-vfold_cv(breastCa_Re, v = 10)

Model spec

#least square
lm_spec <-
    linear_reg() %>% 
    set_engine(engine = 'lm') %>% 
    set_mode('regression')

#LASSO
lm_lasso_spec <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>% ## mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>% #note we are using a different engine
  set_mode('regression')

Recipes & workflows

#least square
least_rec <- recipe(area_mean ~ ., data = breastCa_Re) %>%
    step_corr(all_predictors()) %>% 
    step_nzv(all_predictors()) %>% # removes variables with the same value
    step_normalize(all_numeric_predictors()) %>% # important standardization step for LASSO
    step_dummy(all_nominal_predictors())

least_lm_wf <- workflow() %>%
    add_recipe(least_rec) %>%
    add_model(lm_spec)
    
#LASSO
lasso_wf<- workflow() %>% 
  add_recipe(least_rec) %>%
  add_model(lm_lasso_spec) 

Fit & tune models

#least square
least_fit <- fit(least_lm_wf, data = breastCa_Re) 

least_fit %>% tidy()
#LASSO
#tune
penalty_grid <- grid_regular(
  penalty(range = c(-3, 1)), #log10 transformed 
  levels = 30)

tune_output <- tune_grid( # new function for tuning hyperparameters
  lasso_wf, # workflow
  resamples = breastCa_Re_CV, # cv folds
  metrics = metric_set(rmse, mae),
  grid = penalty_grid # penalty grid defined above
)

#fit
best_se_penalty <- select_by_one_std_err(tune_output, metric = 'mae', desc(penalty))
final_wf_se <- finalize_workflow(lasso_wf, best_se_penalty)
lasso_fit <- fit(final_wf_se , data = breastCa_Re)
lasso_fit %>% tidy()

Calculate and collect CV metrics

# Least Square model
least_fit_cv <- fit_resamples(least_lm_wf,
  resamples = breastCa_Re_CV, 
  metrics = metric_set(rmse, mae)
)

least_fit_cv %>% collect_metrics(summarize = TRUE)
# LASSO model
tune_output %>% 
  collect_metrics() %>% 
  filter(penalty == (best_se_penalty 
                     %>% pull(penalty)))

Residual Plots

#least square
least_fit_output <- least_fit %>%
  predict(new_data = breastCa_Re) %>%
  bind_cols(breastCa_Re) %>%
  mutate(resid = area_mean - .pred)

ggplot(least_fit_output, aes(x = .pred, y = resid)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0, color = "red") +
  labs(x = "Fitted values", y = "Residuals") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = concavity_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Concavity mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = compactness_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Compactness mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = fractal_dimension_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Fractal dimension mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = radius_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Radius mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

#LASSO
lasso_fit_output <- lasso_fit %>%
  predict(new_data = breastCa_Re) %>%
  bind_cols(breastCa_Re) %>%
  mutate(resid = area_mean - .pred)

ggplot(lasso_fit_output, aes(x = .pred, y = resid)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0, color = "red") +
  labs(x = "Fitted values", y = "Residuals") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = concavity_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Concavity mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = compactness_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Compactness mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = fractal_dimension_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Fractal dimension mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = radius_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Radius mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

Accounting for nonlinearity

set.seed(123)

gam_spec <- 
  gen_additive_mod() %>%
  set_engine(engine = 'mgcv') %>%
  set_mode('regression') 

gam_mod <- fit(gam_spec,
    area_mean ~ s(radius_mean)+texture_mean+s(perimeter_mean)+smoothness_mean+compactness_mean+concave_points_mean+concavity_mean+symmetry_mean+fractal_dimension_mean,
    data = breastCa_Re_new
)

par(mfrow=c(2,2))
gam_mod %>% pluck('fit') %>% mgcv::gam.check() 

## 
## Method: GCV   Optimizer: magic
## Smoothing parameter selection converged after 19 iterations.
## The RMS GCV score gradient at convergence was 3.693889e-05 .
## The Hessian was positive definite.
## Model rank =  26 / 26 
## 
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
## 
##                     k'  edf k-index p-value  
## s(radius_mean)    9.00 8.73    0.91   0.015 *
## s(perimeter_mean) 9.00 9.00    0.96   0.170  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
gam_mod %>% pluck('fit') %>% summary()
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## area_mean ~ s(radius_mean) + texture_mean + s(perimeter_mean) + 
##     smoothness_mean + compactness_mean + concave_points_mean + 
##     concavity_mean + symmetry_mean + fractal_dimension_mean
## 
## Parametric coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             676.8849     9.4284  71.792  < 2e-16 ***
## texture_mean              0.2863     0.1136   2.521 0.012000 *  
## smoothness_mean         176.4116    55.3592   3.187 0.001522 ** 
## compactness_mean       -559.1059    41.8880 -13.348  < 2e-16 ***
## concave_points_mean    -193.1237    55.0443  -3.509 0.000488 ***
## concavity_mean           75.1818    19.7185   3.813 0.000153 ***
## symmetry_mean            24.5876    21.8517   1.125 0.261001    
## fractal_dimension_mean  193.2815   164.7137   1.173 0.241134    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                     edf Ref.df     F p-value    
## s(radius_mean)    8.726  8.982 79.72  <2e-16 ***
## s(perimeter_mean) 9.000  9.000 36.36  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.999   Deviance explained = 99.9%
## GCV = 115.47  Scale est. = 110.25    n = 569
ns_rec <- least_rec %>%
  step_ns(x, deg_free = 9)
ns9_wf <- workflow()  %>%
  add_recipe(ns_rec) %>%
  add_model(lm_spec)

hist(breastCa_Re_new$area_mean)

gam_mod %>% pluck('fit') %>% plot()

Evaluation of the GAM model on Test Data

gam_test_output <- gam_mod %>%
  predict(new_data=breastCa_Re_new) %>%
  bind_cols(breastCa_Re_new %>% select(area_mean))

gam_test_output %>%
  rmse(truth=area_mean, estimate=.pred) 
gam_test_output %>%
  mae(truth=area_mean, estimate=.pred) 

Classification

Data Cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(-c(13:22)) %>% 
  select(-1)

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-10)

LASSO and Logistic Regression

Implete Lasso Logistic Regression in tidymodels

# Make sure you set reference level (to the outcome you are NOT interested in)
breastCa_Re_new2 <- breastCa_Re_new%>%
  mutate(diagnosis = relevel(factor(diagnosis ), ref='B')) #set reference level

data_cv10 <- vfold_cv(breastCa_Re_new2, v = 10)


# Logistic LASSO Regression Model Spec
logistic_lasso_spec_tune <- logistic_reg() %>%
    set_engine('glmnet') %>%
    set_args(mixture = 1, penalty = tune()) %>%
    set_mode('classification')

# Recipe
logistic_rec <- recipe(diagnosis ~ ., data = breastCa_Re_new2) %>%
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors())

# Workflow (Recipe + Model)
log_lasso_wf <- workflow() %>% 
    add_recipe(logistic_rec) %>%
    add_model(logistic_lasso_spec_tune) 

# Tune Model (trying a variety of values of Lambda penalty)
penalty_grid <- grid_regular(
  penalty(range = c(-5, 1)), #log10 transformed  (kept moving min down from 0)
  levels = 100)

tune_output <- tune_grid( 
  log_lasso_wf, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(roc_auc,accuracy),
  control = control_resamples(save_pred = TRUE, event_level = 'second'),
  grid = penalty_grid # penalty grid defined above
)

# Visualize Model Evaluation Metrics from Tuning
autoplot(tune_output) + theme_classic()

Inspecting the Model

best_se_penalty <- select_by_one_std_err(tune_output, metric = 'roc_auc', desc(penalty)) # choose penalty value based on the largest penalty within 1 se of the highest CV roc_auc
final_fit_se <- finalize_workflow(log_lasso_wf, best_se_penalty) %>% # incorporates penalty value to workflow 
    fit(data = breastCa_Re_new2)

final_fit_se %>% tidy()
final_fit_se %>% tidy() %>%
  filter(estimate == 0)
#variable importance
glmnet_output <- final_fit_se %>% extract_fit_engine()
    
# Create a boolean matrix (predictors x lambdas) of variable exclusion
bool_predictor_exclude <- glmnet_output$beta==0

# Loop over each variable
var_imp <- sapply(seq_len(nrow(bool_predictor_exclude)), function(row) {
    # Extract coefficient path (sorted from highest to lowest lambda)
    this_coeff_path <- bool_predictor_exclude[row,]
    # Compute and return the # of lambdas until this variable is out forever
    ncol(bool_predictor_exclude) - which.min(this_coeff_path) + 1
})

# Create a dataset of this information and sort
var_imp_data <- tibble(
    var_name = rownames(bool_predictor_exclude),
    var_imp = var_imp
)
var_imp_data %>% arrange(desc(var_imp))

Evaluation Metrics

# CV results for "best lambda"
tune_output %>%
    collect_metrics() %>%
    filter(penalty == best_se_penalty %>% pull(penalty))
# Count up number of B and M in the training data
breastCa_Re_new2 %>%
    count(diagnosis) # Name of the outcome variable goes inside count()
#Compute the NIR
NIR<- 357/(357+212)
NIR
## [1] 0.6274165

Threshold

# Soft Predictions on Training Data
final_output <-
  final_fit_se %>% predict(new_data = breastCa_Re_new2, type = 'prob') %>%     bind_cols(breastCa_Re_new2)



final_output %>%
  ggplot(aes(x = diagnosis, y = .pred_M)) +
  geom_boxplot()

# Use soft predictions
final_output %>%
    roc_curve(diagnosis,.pred_M,event_level = 'second') %>%
    autoplot()

# thresholds in terms of reference level
threshold_output <- final_output %>%
    threshold_perf(truth = diagnosis, estimate = .pred_B, thresholds = seq(0,1,by=.01)) 

# J-index v. threshold for not M
threshold_output %>%
    filter(.metric == 'j_index') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'J-index', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'j_index') %>%
    arrange(desc(.estimate))
# Distance v. threshold for not M

threshold_output %>%
    filter(.metric == 'distance') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'Distance', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'distance') %>%
    arrange(.estimate)
log_metrics <- metric_set(accuracy,sens,yardstick::spec)

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_B, levels(diagnosis), threshold = .64)) %>%
    log_metrics(truth = diagnosis, estimate = .pred_class, event_level = 'second')

Random Forest

Building Random Forest

# Model Specification
rf_spec <- rand_forest() %>%
  set_engine(engine = 'ranger') %>% 
  set_args(mtry = NULL, # size of random subset of variables; default is floor(sqrt(ncol(x)))
           trees = 1000, # Number of trees
           min_n = 2,
           probability = FALSE, # FALSE: hard predictions
           importance = 'impurity') %>% 
  set_mode('classification') # change this for regression tree

# Recipe
data_rec <- recipe(diagnosis ~ ., data = breastCa_Re_new2)

# Workflows
data_wf_mtry2 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 2)) %>%
  add_recipe(data_rec)

# Create workflows for mtry = 4 , 10, and 20
data_wf_mtry4 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 4)) %>%
  add_recipe(data_rec)

data_wf_mtry10 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 10)) %>%
  add_recipe(data_rec)

data_wf_mtry20 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 20)) %>%
  add_recipe(data_rec)
# Fit Models

set.seed(123) # make sure to run this before each fit so that you have the same 1000 trees
data_fit_mtry2 <- fit(data_wf_mtry2, data = breastCa_Re_new2)

set.seed(123)
data_fit_mtry4 <- fit(data_wf_mtry4, data = breastCa_Re_new2)

set.seed(123) 
data_fit_mtry10 <- fit(data_wf_mtry10, data = breastCa_Re_new2)

set.seed(123)
data_fit_mtry20 <- fit(data_wf_mtry20, data = breastCa_Re_new2)
# Custom Function to get OOB predictions, true observed outcomes and add a model label
rf_OOB_output <- function(fit_model, model_label, truth){
    tibble(
          .pred_diagnosis = fit_model %>% extract_fit_engine() %>% pluck('predictions'), #OOB predictions
          diagnosis = truth,
          model = model_label
      )
}

#check out the function output
rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis))
# Evaluate OOB Metrics

data_rf_OOB_output <- bind_rows(
    rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry4,'mtry4', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry10,'mtry10', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry20,'mtry20', breastCa_Re_new2 %>% pull(diagnosis))
)


data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = diagnosis, estimate = .pred_diagnosis)

Preliminary interpretation

data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = diagnosis, estimate =.pred_diagnosis) %>%
  mutate(mtry = as.numeric(stringr::str_replace(model,'mtry',''))) %>%
  ggplot(aes(x = mtry, y = .estimate )) + 
  geom_point() +
  geom_line() +
  theme_classic()

Evaluating the forest

data_fit_mtry2
## == Workflow [trained] ==========================================================
## Preprocessor: Recipe
## Model: rand_forest()
## 
## -- Preprocessor ----------------------------------------------------------------
## 0 Recipe Steps
## 
## -- Model -----------------------------------------------------------------------
## Ranger result
## 
## Call:
##  ranger::ranger(x = maybe_data_frame(x), y = y, mtry = min_cols(~2,      x), num.trees = ~1000, min.node.size = min_rows(~2, x), probability = ~FALSE,      importance = ~"impurity", num.threads = 1, verbose = FALSE,      seed = sample.int(10^5, 1)) 
## 
## Type:                             Classification 
## Number of trees:                  1000 
## Sample size:                      569 
## Number of independent variables:  20 
## Mtry:                             2 
## Target node size:                 2 
## Variable importance mode:         impurity 
## Splitrule:                        gini 
## OOB prediction error:             3.51 %
rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis)) %>%
    conf_mat(truth = diagnosis, estimate= .pred_diagnosis)
##           Truth
## Prediction   B   M
##          B 351  14
##          M   6 198

Variable importance measures

data_fit_mtry2 %>% 
    extract_fit_engine() %>% 
    vip(num_features = 30) + theme_classic()

ggplot(breastCa_Re_new2, aes(x = diagnosis, y = area_worst)) +
    geom_violin() + theme_classic()

ggplot(breastCa_Re_new2, aes(x = diagnosis, y = fractal_dimension_mean)) +
    geom_violin() + theme_classic()

#intermediate important
ggplot(breastCa_Re_new2, aes(x = diagnosis, y = perimeter_mean)) +
    geom_violin() + theme_classic()

Clustering

Data Cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(-c(13:22)) %>% 
  select(-1)

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-10) 
ggplot(breastCa_Re_new, aes(x = perimeter_mean, y = `concave points_worst`)) +
    geom_point() +
    theme_classic()

K-means clustering on perimeter_mean and concave points_worst

# Select just the perimeter_mean and concave points_worst variables
breastCa_Re_new_sub <- breastCa_Re_new %>%
    select(perimeter_mean, `concave points_worst`)

# Run k-means for k = centers = 2
set.seed(253)
kclust_k2 <- kmeans(breastCa_Re_new_sub, centers = 2)

# Display the cluster assignments
kclust_k2$cluster
##   [1] 2 2 2 1 2 1 2 1 1 1 2 2 2 2 1 1 1 2 2 1 1 1 2 2 2 2 1 2 2 2 2 1 2 2 2 2 1
##  [38] 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1
##  [75] 1 2 1 2 2 1 1 1 2 2 1 2 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1
## [112] 1 1 1 1 1 1 1 2 2 1 2 2 1 1 1 1 2 1 2 1 1 2 2 2 1 1 1 1 1 1 2 1 1 1 1 1 1
## [149] 1 1 1 1 1 1 1 1 2 2 1 1 1 2 2 1 2 1 1 2 2 1 1 1 2 1 1 1 1 2 1 1 2 2 1 1 1
## [186] 1 2 1 1 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1 1 1 1 2 1 1 2 1 2 2 1 1 1 1 2 2 1 1
## [223] 1 2 1 1 1 1 1 1 2 1 1 2 1 1 2 2 1 2 1 1 1 1 2 1 1 1 1 1 2 1 2 2 2 1 2 2 2
## [260] 2 2 2 2 1 2 2 1 1 1 1 1 1 2 1 2 1 1 2 1 1 2 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1
## [297] 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 2 1 1 1 1 2 2 2 1 1
## [334] 1 1 2 1 2 1 2 1 1 1 2 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 2 1 2 2 1 2 2
## [371] 2 1 2 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 1 2
## [408] 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 2 1 1 1 1 1 1 1 2 1 1
## [445] 2 1 2 1 1 2 1 2 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1
## [482] 1 1 1 2 1 1 2 1 2 1 2 2 1 1 1 1 1 2 2 1 1 1 2 1 1 1 1 2 2 1 1 1 1 1 1 2 2
## [519] 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [556] 1 1 1 1 1 1 1 2 2 2 2 2 2 1
# Add a variable (kclust_k2) to the original dataset 
# containing the cluster assignments
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_2 = factor(kclust_k2$cluster))
# Visualize the cluster assignments on the original scatterplot
originalClusterPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_2,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(originalClusterPlot  , tooltip = c( "text"))

Addressing variable scale

# Run k-means on the *scaled* data (all variables have SD = 1)
set.seed(253)
kclust_k2_scale <- kmeans(scale(breastCa_Re_new_sub), centers = 2)
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_2_scale = factor(kclust_k2_scale$cluster))

# Visualize the new cluster assignments
scaledClusterPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_2,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(scaledClusterPlot  , tooltip = c( "text"))

Clustering on more variables

# Select the variables to be used in clustering
breastCa_Re_new_sub2 <- breastCa_Re_new %>%
    select(c(2:21))

# Look at summary statistics of the 3 variables
summary(breastCa_Re_new_sub2)
##   radius_mean      texture_mean   perimeter_mean     area_mean     
##  Min.   : 6.981   Min.   : 9.71   Min.   : 43.79   Min.   : 143.5  
##  1st Qu.:11.700   1st Qu.:16.17   1st Qu.: 75.17   1st Qu.: 420.3  
##  Median :13.370   Median :18.84   Median : 86.24   Median : 551.1  
##  Mean   :14.127   Mean   :19.29   Mean   : 91.97   Mean   : 654.9  
##  3rd Qu.:15.780   3rd Qu.:21.80   3rd Qu.:104.10   3rd Qu.: 782.7  
##  Max.   :28.110   Max.   :39.28   Max.   :188.50   Max.   :2501.0  
##  smoothness_mean   compactness_mean  concavity_mean    concave points_mean
##  Min.   :0.05263   Min.   :0.01938   Min.   :0.00000   Min.   :0.00000    
##  1st Qu.:0.08637   1st Qu.:0.06492   1st Qu.:0.02956   1st Qu.:0.02031    
##  Median :0.09587   Median :0.09263   Median :0.06154   Median :0.03350    
##  Mean   :0.09636   Mean   :0.10434   Mean   :0.08880   Mean   :0.04892    
##  3rd Qu.:0.10530   3rd Qu.:0.13040   3rd Qu.:0.13070   3rd Qu.:0.07400    
##  Max.   :0.16340   Max.   :0.34540   Max.   :0.42680   Max.   :0.20120    
##  fractal_dimension_mean  radius_worst   texture_worst   perimeter_worst 
##  Min.   :0.04996        Min.   : 7.93   Min.   :12.02   Min.   : 50.41  
##  1st Qu.:0.05770        1st Qu.:13.01   1st Qu.:21.08   1st Qu.: 84.11  
##  Median :0.06154        Median :14.97   Median :25.41   Median : 97.66  
##  Mean   :0.06280        Mean   :16.27   Mean   :25.68   Mean   :107.26  
##  3rd Qu.:0.06612        3rd Qu.:18.79   3rd Qu.:29.72   3rd Qu.:125.40  
##  Max.   :0.09744        Max.   :36.04   Max.   :49.54   Max.   :251.20  
##    area_worst     smoothness_worst  compactness_worst concavity_worst 
##  Min.   : 185.2   Min.   :0.07117   Min.   :0.02729   Min.   :0.0000  
##  1st Qu.: 515.3   1st Qu.:0.11660   1st Qu.:0.14720   1st Qu.:0.1145  
##  Median : 686.5   Median :0.13130   Median :0.21190   Median :0.2267  
##  Mean   : 880.6   Mean   :0.13237   Mean   :0.25427   Mean   :0.2722  
##  3rd Qu.:1084.0   3rd Qu.:0.14600   3rd Qu.:0.33910   3rd Qu.:0.3829  
##  Max.   :4254.0   Max.   :0.22260   Max.   :1.05800   Max.   :1.2520  
##  concave points_worst symmetry_worst   fractal_dimension_worst
##  Min.   :0.00000      Min.   :0.1565   Min.   :0.05504        
##  1st Qu.:0.06493      1st Qu.:0.2504   1st Qu.:0.07146        
##  Median :0.09993      Median :0.2822   Median :0.08004        
##  Mean   :0.11461      Mean   :0.2901   Mean   :0.08395        
##  3rd Qu.:0.16140      3rd Qu.:0.3179   3rd Qu.:0.09208        
##  Max.   :0.29100      Max.   :0.6638   Max.   :0.20750        
##  concave_points_mean
##  Min.   :0.00000    
##  1st Qu.:0.02031    
##  Median :0.03350    
##  Mean   :0.04892    
##  3rd Qu.:0.07400    
##  Max.   :0.20120
set.seed(253)
kclust_k2_allvars <- kmeans(scale(breastCa_Re_new_sub2), centers = 2)

breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_k2_allvars = factor(kclust_k2_allvars$cluster))


breastCa_Re_new %>%
  count(diagnosis,kclust_k2_allvars)

Interpreting the clusters

breastCa_Re_new %>%
    group_by(kclust_k2_allvars) %>%
    summarize(across(c(2:21), mean))

Picking k

# Data-specific function to cluster and calculate total within-cluster SS
breastCa_Re_new_cluster_ss <- function(k){
    # Perform clustering
    kclust <- kmeans(scale(breastCa_Re_new_sub2), centers = k)

    # Return the total within-cluster sum of squares
    return(kclust$tot.withinss)
}

tibble(
    k = 1:20,
    tot_wc_ss = purrr::map_dbl(1:20, breastCa_Re_new_cluster_ss)
) %>% 
    ggplot(aes(x = k, y = tot_wc_ss)) +
    geom_point() + 
    geom_line()+
    labs(x = "Number of clusters",y = 'Total within-cluster sum of squares') + 
    theme_classic()

Normalized variables clustering visualizaiton of optimal K

# Run k-means for k = centers = 3
set.seed(253)
kclust_k3 <- kmeans(breastCa_Re_new_sub, centers = 3)

# Display the cluster assignments
kclust_k3$cluster
##   [1] 3 3 3 1 3 2 3 2 2 2 2 2 3 2 2 2 2 2 3 2 2 1 2 3 2 3 2 3 2 3 3 1 3 3 2 2 2
##  [38] 2 2 2 2 1 3 2 2 3 1 2 1 2 1 2 1 3 2 1 3 2 2 1 1 1 2 1 2 2 1 1 1 1 3 1 3 2
##  [75] 1 2 2 3 3 2 1 2 3 3 1 3 2 3 1 2 2 2 2 2 2 3 1 1 1 2 2 1 1 1 1 2 1 1 3 1 1
## [112] 1 2 1 1 1 1 2 2 3 1 3 3 2 2 2 2 3 2 3 1 2 2 2 3 1 1 1 2 1 1 2 1 2 1 1 1 2
## [149] 2 2 2 1 1 1 2 1 3 2 1 1 1 3 3 1 3 2 1 2 3 2 1 2 2 1 1 1 1 2 1 1 3 3 2 1 2
## [186] 1 3 1 1 1 2 1 1 1 2 2 2 3 3 2 1 3 3 2 1 2 1 2 2 2 3 1 3 3 2 2 1 1 3 3 2 2
## [223] 1 2 2 2 1 2 1 2 3 1 1 3 1 2 3 3 2 3 2 1 1 2 3 1 2 2 1 1 3 1 3 3 3 2 3 2 2
## [260] 2 3 2 3 2 2 3 1 2 1 1 2 1 3 1 3 1 1 3 2 2 3 1 3 2 2 1 1 1 1 1 2 2 2 1 1 2
## [297] 1 1 2 1 3 1 3 1 1 1 2 1 2 2 1 2 1 1 1 1 1 3 1 1 1 3 2 3 1 1 2 1 2 2 2 2 1
## [334] 1 1 2 2 3 1 3 2 1 1 3 1 1 1 2 1 1 1 2 3 2 1 1 2 2 1 1 1 2 1 2 2 3 3 1 3 3
## [371] 2 2 3 3 2 2 1 2 2 1 1 1 1 1 2 2 1 2 1 3 1 1 2 3 1 2 2 2 1 1 3 1 2 2 1 1 2
## [408] 2 3 1 1 1 1 2 2 1 1 2 1 1 1 2 1 2 1 1 1 1 1 1 2 1 3 3 2 2 2 2 2 2 1 2 2 1
## [445] 3 1 3 2 2 3 1 3 1 2 1 2 1 2 2 1 2 3 2 1 2 2 2 1 3 1 1 1 2 1 1 2 2 2 1 2 1
## [482] 2 2 2 2 2 2 3 1 2 1 3 3 1 2 2 2 1 3 3 2 2 1 3 1 1 1 1 2 2 1 2 2 2 2 1 3 3
## [519] 2 2 1 3 1 2 1 1 2 1 2 1 1 1 2 3 1 3 2 1 1 1 1 2 2 2 2 2 1 1 1 1 1 1 1 1 2
## [556] 1 1 1 2 1 2 1 2 3 3 3 2 3 1
# Run k-means on the *scaled* data (all variables have SD = 1)
set.seed(253)
kclust_k3_scale <- kmeans(scale(breastCa_Re_new_sub), centers = 3)
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_3_scale = factor(kclust_k3_scale$cluster))

# Visualize the new cluster assignments
newClusterKPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_3_scale,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(newClusterKPlot  , tooltip = c( "text"))

Perform clustering on more variabels with K=3

set.seed(253)
kclust_k3_allvars <- kmeans(scale(breastCa_Re_new_sub2), centers = 3)
#within clusters su of squares
kclust_k3_allvars
## K-means clustering with 3 clusters of sizes 370, 93, 106
## 
## Cluster means:
##   radius_mean texture_mean perimeter_mean   area_mean smoothness_mean
## 1 -0.47294644   -0.2447179    -0.49151681 -0.46834420      -0.3393479
## 2 -0.01575391    0.2342339     0.05989892 -0.08895702       0.9405564
## 3  1.66467260    0.6486969     1.66311907  1.71283356       0.3593111
##   compactness_mean concavity_mean concave points_mean fractal_dimension_mean
## 1       -0.5408815     -0.5826564          -0.5916723             -0.1707039
## 2        1.1632023      0.9455177           0.6854667              1.0983509
## 3        0.8674372      1.2042428           1.4638711             -0.3677942
##   radius_worst texture_worst perimeter_worst  area_worst smoothness_worst
## 1   -0.5121756    -0.2770056      -0.5286914 -0.49367809       -0.3671331
## 2    0.1171932     0.4474237       0.2045023  0.01879255        1.0989419
## 3    1.6849621     0.5743552       1.6660104  1.70672818        0.3173362
##   compactness_worst concavity_worst concave points_worst symmetry_worst
## 1        -0.5208214      -0.5605965           -0.6036665     -0.3364717
## 2         1.3364447       1.2237501            0.9683800      1.0287464
## 3         0.6454203       0.8831314            1.2575212      0.2718973
##   fractal_dimension_worst concave_points_mean
## 1             -0.37590256          -0.5916723
## 2              1.43420972           0.6854667
## 3              0.05379665           1.4638711
## 
## Clustering vector:
##   [1] 2 3 3 2 3 2 3 2 2 2 1 2 3 1 2 2 1 2 3 1 1 1 2 3 3 2 2 3 2 3 3 2 3 3 2 2 2
##  [38] 1 1 2 1 2 3 2 2 3 1 2 1 1 1 1 1 3 1 1 3 2 1 1 1 1 2 1 2 2 1 1 2 1 3 1 2 1
##  [75] 1 1 1 3 3 1 1 2 3 3 1 3 1 3 1 1 1 1 1 1 2 3 1 1 1 2 1 1 1 1 1 2 1 1 3 1 1
## [112] 1 2 1 1 1 1 2 2 1 1 3 3 1 1 1 1 3 2 3 1 2 2 1 3 1 1 1 2 1 1 1 1 1 1 1 2 1
## [149] 1 1 1 2 2 1 1 1 3 1 1 1 1 3 3 1 3 1 1 1 3 1 1 1 2 1 1 1 1 2 1 1 3 3 2 1 1
## [186] 1 3 1 1 1 2 1 1 2 2 1 2 1 3 2 1 3 3 2 1 1 1 1 2 1 3 1 3 1 2 2 1 1 3 3 1 1
## [223] 1 2 1 1 1 1 1 2 2 1 1 3 1 1 3 3 1 3 1 1 2 1 3 1 1 2 1 1 3 1 3 3 3 1 3 2 2
## [260] 2 3 1 3 1 3 3 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 3 2 1 1 1 1 1 1 1 1 1 1 1 1
## [297] 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 2 1 1 3 1 3 1 1 1 1 2 2 2 1 1
## [334] 1 1 3 1 3 1 3 1 1 1 3 1 1 1 1 1 1 1 2 3 2 1 1 1 1 1 1 1 1 1 1 1 3 3 1 3 3
## [371] 2 1 3 3 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 3 1 1 2 3 1 1 1 1 1 1 2 1 1 1 1 1 1
## [408] 1 3 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 1 1 1 1 1 2 1 3 3 1 2 1 1 1 1 1 3 1 1
## [445] 3 1 3 1 1 3 1 3 1 1 1 1 1 1 1 1 3 3 1 1 1 2 1 1 3 2 1 1 1 1 1 1 1 1 1 2 1
## [482] 1 1 1 1 2 1 3 1 1 1 1 3 1 1 1 2 1 3 3 1 2 1 3 2 2 1 1 1 2 1 1 2 1 1 1 3 3
## [519] 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 3 1 3 2 2 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1
## [556] 1 1 1 1 1 1 1 2 3 3 3 1 3 1
## 
## Within cluster sum of squares by cluster:
## [1] 2779.416 1314.407 1403.885
##  (between_SS / total_SS =  51.6 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_k3_allvars = factor(kclust_k3_allvars$cluster))


breastCa_Re_new %>%
  count(diagnosis,kclust_k3_allvars)

Interpreting the clusters with k=3

breastCa_Re_new %>%
    group_by(kclust_k3_allvars) %>%
    summarize(across(c(2:21), mean))
LS0tDQp0aXRsZTogIlN0YXQtMjUzIEZpbmFsIFByb2plY3QiDQphdXRob3I6ICJKZW5ueSBMaSwgTGl6IENhbywgS3Jpc3R5IE1hIg0KZGF0ZTogJzIwMjItMDQtMDcnDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHRpZHkgPSBUUlVFKQ0KYGBgDQoNCiMgR2VuZXJhbCBTZXQgVVANCg0KIyMgTGlicmFyeQ0KYGBge3IsIGxpYnJhcnl9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHltb2RlbHMpIA0KbGlicmFyeShwcm9iYWJseSkNCmxpYnJhcnkodmlwKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KENsdXN0ZXJSKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeSh2aXApDQp0aWR5bW9kZWxzX3ByZWZlcigpDQp0aGVtZV9zZXQodGhlbWVfYncoKSkgICAgICAgDQpTeXMuc2V0bG9jYWxlKCJMQ19USU1FIiwgIkVuZ2xpc2giKQ0Kc2V0LnNlZWQoNzQpDQpgYGANCg0KIyMgUmVhZCBpbiBkYXRhDQoNCmBgYHtyLCByZWFkaW5nIGRhdGF9DQpicmVhc3RDYTwtcmVhZF9jc3YoZmlsZSA9ICJicmVhc3QtY2FuY2VyLmNzdiIpDQpgYGANCg0KIyBSZWdyZXNzaW9uIChMZWFzdCBTcXVhcmUgJiBMQVNTTykNCg0KIyMgRGF0YSBjbGVhbmluZw0KYGBge3J9DQpicmVhc3RDYV9SZTwtYnJlYXN0Q2EgJT4lIA0KICBkcm9wX25hKCkgJT4lIA0KICBzZWxlY3QocmFkaXVzX21lYW46ZnJhY3RhbF9kaW1lbnNpb25fbWVhbikgDQoNCmJyZWFzdENhX1JlX25ldzwtYnJlYXN0Q2FfUmUlPiUNCiAgbXV0YXRlKGNvbmNhdmVfcG9pbnRzX21lYW49YGNvbmNhdmUgcG9pbnRzX21lYW5gKSU+JQ0KICBzZWxlY3QoLTgpDQpgYGANCg0KIyMgQ3JlYXRpb24gb2YgY3YgZm9sZHMNCmBgYHtyfQ0KYnJlYXN0Q2FfUmVfQ1Y8LXZmb2xkX2N2KGJyZWFzdENhX1JlLCB2ID0gMTApDQpgYGANCg0KIyMgTW9kZWwgc3BlYw0KYGBge3J9DQojbGVhc3Qgc3F1YXJlDQpsbV9zcGVjIDwtDQogICAgbGluZWFyX3JlZygpICU+JSANCiAgICBzZXRfZW5naW5lKGVuZ2luZSA9ICdsbScpICU+JSANCiAgICBzZXRfbW9kZSgncmVncmVzc2lvbicpDQoNCiNMQVNTTw0KbG1fbGFzc29fc3BlYyA8LSANCiAgbGluZWFyX3JlZygpICU+JQ0KICBzZXRfYXJncyhtaXh0dXJlID0gMSwgcGVuYWx0eSA9IHR1bmUoKSkgJT4lICMjIG1peHR1cmUgPSAxIGluZGljYXRlcyBMYXNzbw0KICBzZXRfZW5naW5lKGVuZ2luZSA9ICdnbG1uZXQnKSAlPiUgI25vdGUgd2UgYXJlIHVzaW5nIGEgZGlmZmVyZW50IGVuZ2luZQ0KICBzZXRfbW9kZSgncmVncmVzc2lvbicpDQpgYGANCg0KIyMgUmVjaXBlcyAmIHdvcmtmbG93cw0KYGBge3J9DQojbGVhc3Qgc3F1YXJlDQpsZWFzdF9yZWMgPC0gcmVjaXBlKGFyZWFfbWVhbiB+IC4sIGRhdGEgPSBicmVhc3RDYV9SZSkgJT4lDQogICAgc3RlcF9jb3JyKGFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgICBzdGVwX256dihhbGxfcHJlZGljdG9ycygpKSAlPiUgIyByZW1vdmVzIHZhcmlhYmxlcyB3aXRoIHRoZSBzYW1lIHZhbHVlDQogICAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgIyBpbXBvcnRhbnQgc3RhbmRhcmRpemF0aW9uIHN0ZXAgZm9yIExBU1NPDQogICAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpDQoNCmxlYXN0X2xtX3dmIDwtIHdvcmtmbG93KCkgJT4lDQogICAgYWRkX3JlY2lwZShsZWFzdF9yZWMpICU+JQ0KICAgIGFkZF9tb2RlbChsbV9zcGVjKQ0KICAgIA0KI0xBU1NPDQpsYXNzb193ZjwtIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKGxlYXN0X3JlYykgJT4lDQogIGFkZF9tb2RlbChsbV9sYXNzb19zcGVjKSANCmBgYA0KDQojIyBGaXQgJiB0dW5lIG1vZGVscw0KYGBge3J9DQojbGVhc3Qgc3F1YXJlDQpsZWFzdF9maXQgPC0gZml0KGxlYXN0X2xtX3dmLCBkYXRhID0gYnJlYXN0Q2FfUmUpIA0KDQpsZWFzdF9maXQgJT4lIHRpZHkoKQ0KDQojTEFTU08NCiN0dW5lDQpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKA0KICBwZW5hbHR5KHJhbmdlID0gYygtMywgMSkpLCAjbG9nMTAgdHJhbnNmb3JtZWQgDQogIGxldmVscyA9IDMwKQ0KDQp0dW5lX291dHB1dCA8LSB0dW5lX2dyaWQoICMgbmV3IGZ1bmN0aW9uIGZvciB0dW5pbmcgaHlwZXJwYXJhbWV0ZXJzDQogIGxhc3NvX3dmLCAjIHdvcmtmbG93DQogIHJlc2FtcGxlcyA9IGJyZWFzdENhX1JlX0NWLCAjIGN2IGZvbGRzDQogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSksDQogIGdyaWQgPSBwZW5hbHR5X2dyaWQgIyBwZW5hbHR5IGdyaWQgZGVmaW5lZCBhYm92ZQ0KKQ0KDQojZml0DQpiZXN0X3NlX3BlbmFsdHkgPC0gc2VsZWN0X2J5X29uZV9zdGRfZXJyKHR1bmVfb3V0cHV0LCBtZXRyaWMgPSAnbWFlJywgZGVzYyhwZW5hbHR5KSkNCmZpbmFsX3dmX3NlIDwtIGZpbmFsaXplX3dvcmtmbG93KGxhc3NvX3dmLCBiZXN0X3NlX3BlbmFsdHkpDQpsYXNzb19maXQgPC0gZml0KGZpbmFsX3dmX3NlICwgZGF0YSA9IGJyZWFzdENhX1JlKQ0KbGFzc29fZml0ICU+JSB0aWR5KCkNCg0KYGBgDQoNCiMjIENhbGN1bGF0ZSBhbmQgY29sbGVjdCBDViBtZXRyaWNzDQoNCmBgYHtyfQ0KIyBMZWFzdCBTcXVhcmUgbW9kZWwNCmxlYXN0X2ZpdF9jdiA8LSBmaXRfcmVzYW1wbGVzKGxlYXN0X2xtX3dmLA0KICByZXNhbXBsZXMgPSBicmVhc3RDYV9SZV9DViwgDQogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSkNCikNCg0KbGVhc3RfZml0X2N2ICU+JSBjb2xsZWN0X21ldHJpY3Moc3VtbWFyaXplID0gVFJVRSkNCmBgYA0KYGBge3J9DQojIExBU1NPIG1vZGVsDQp0dW5lX291dHB1dCAlPiUgDQogIGNvbGxlY3RfbWV0cmljcygpICU+JSANCiAgZmlsdGVyKHBlbmFsdHkgPT0gKGJlc3Rfc2VfcGVuYWx0eSANCiAgICAgICAgICAgICAgICAgICAgICU+JSBwdWxsKHBlbmFsdHkpKSkNCmBgYA0KDQojIyBSZXNpZHVhbCBQbG90cyANCmBgYHtyfQ0KI2xlYXN0IHNxdWFyZQ0KbGVhc3RfZml0X291dHB1dCA8LSBsZWFzdF9maXQgJT4lDQogIHByZWRpY3QobmV3X2RhdGEgPSBicmVhc3RDYV9SZSkgJT4lDQogIGJpbmRfY29scyhicmVhc3RDYV9SZSkgJT4lDQogIG11dGF0ZShyZXNpZCA9IGFyZWFfbWVhbiAtIC5wcmVkKQ0KDQpnZ3Bsb3QobGVhc3RfZml0X291dHB1dCwgYWVzKHggPSAucHJlZCwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHggPSAiRml0dGVkIHZhbHVlcyIsIHkgPSAiUmVzaWR1YWxzIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGVhc3RfZml0X291dHB1dCwgYWVzKHggPSBjb25jYXZpdHlfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkNvbmNhdml0eSBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGVhc3RfZml0X291dHB1dCwgYWVzKHggPSBjb21wYWN0bmVzc19tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiQ29tcGFjdG5lc3MgbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gZnJhY3RhbF9kaW1lbnNpb25fbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkZyYWN0YWwgZGltZW5zaW9uIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsZWFzdF9maXRfb3V0cHV0LCBhZXMoeCA9IHJhZGl1c19tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiUmFkaXVzIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCmBgYHtyfQ0KI0xBU1NPDQpsYXNzb19maXRfb3V0cHV0IDwtIGxhc3NvX2ZpdCAlPiUNCiAgcHJlZGljdChuZXdfZGF0YSA9IGJyZWFzdENhX1JlKSAlPiUNCiAgYmluZF9jb2xzKGJyZWFzdENhX1JlKSAlPiUNCiAgbXV0YXRlKHJlc2lkID0gYXJlYV9tZWFuIC0gLnByZWQpDQoNCmdncGxvdChsYXNzb19maXRfb3V0cHV0LCBhZXMoeCA9IC5wcmVkLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIGxhYnMoeCA9ICJGaXR0ZWQgdmFsdWVzIiwgeSA9ICJSZXNpZHVhbHMiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsYXNzb19maXRfb3V0cHV0LCBhZXMoeCA9IGNvbmNhdml0eV9tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiQ29uY2F2aXR5IG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsYXNzb19maXRfb3V0cHV0LCBhZXMoeCA9IGNvbXBhY3RuZXNzX21lYW4sIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGxhYnMoeCA9ICJDb21wYWN0bmVzcyBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSBmcmFjdGFsX2RpbWVuc2lvbl9tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiRnJhY3RhbCBkaW1lbnNpb24gbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxhc3NvX2ZpdF9vdXRwdXQsIGFlcyh4ID0gcmFkaXVzX21lYW4sIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGxhYnMoeCA9ICJSYWRpdXMgbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMgQWNjb3VudGluZyBmb3Igbm9ubGluZWFyaXR5IA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCg0KZ2FtX3NwZWMgPC0gDQogIGdlbl9hZGRpdGl2ZV9tb2QoKSAlPiUNCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAnbWdjdicpICU+JQ0KICBzZXRfbW9kZSgncmVncmVzc2lvbicpIA0KDQpnYW1fbW9kIDwtIGZpdChnYW1fc3BlYywNCiAgICBhcmVhX21lYW4gfiBzKHJhZGl1c19tZWFuKSt0ZXh0dXJlX21lYW4rcyhwZXJpbWV0ZXJfbWVhbikrc21vb3RobmVzc19tZWFuK2NvbXBhY3RuZXNzX21lYW4rY29uY2F2ZV9wb2ludHNfbWVhbitjb25jYXZpdHlfbWVhbitzeW1tZXRyeV9tZWFuK2ZyYWN0YWxfZGltZW5zaW9uX21lYW4sDQogICAgZGF0YSA9IGJyZWFzdENhX1JlX25ldw0KKQ0KDQpwYXIobWZyb3c9YygyLDIpKQ0KZ2FtX21vZCAlPiUgcGx1Y2soJ2ZpdCcpICU+JSBtZ2N2OjpnYW0uY2hlY2soKSANCmdhbV9tb2QgJT4lIHBsdWNrKCdmaXQnKSAlPiUgc3VtbWFyeSgpDQoNCm5zX3JlYyA8LSBsZWFzdF9yZWMgJT4lDQogIHN0ZXBfbnMoeCwgZGVnX2ZyZWUgPSA5KQ0KbnM5X3dmIDwtIHdvcmtmbG93KCkgICU+JQ0KICBhZGRfcmVjaXBlKG5zX3JlYykgJT4lDQogIGFkZF9tb2RlbChsbV9zcGVjKQ0KDQpoaXN0KGJyZWFzdENhX1JlX25ldyRhcmVhX21lYW4pDQoNCmdhbV9tb2QgJT4lIHBsdWNrKCdmaXQnKSAlPiUgcGxvdCgpDQpgYGANCg0KIyMgRXZhbHVhdGlvbiBvZiB0aGUgR0FNIG1vZGVsIG9uIFRlc3QgRGF0YSANCg0KYGBge3J9DQpnYW1fdGVzdF9vdXRwdXQgPC0gZ2FtX21vZCAlPiUNCiAgcHJlZGljdChuZXdfZGF0YT1icmVhc3RDYV9SZV9uZXcpICU+JQ0KICBiaW5kX2NvbHMoYnJlYXN0Q2FfUmVfbmV3ICU+JSBzZWxlY3QoYXJlYV9tZWFuKSkNCg0KZ2FtX3Rlc3Rfb3V0cHV0ICU+JQ0KICBybXNlKHRydXRoPWFyZWFfbWVhbiwgZXN0aW1hdGU9LnByZWQpIA0KDQpnYW1fdGVzdF9vdXRwdXQgJT4lDQogIG1hZSh0cnV0aD1hcmVhX21lYW4sIGVzdGltYXRlPS5wcmVkKSANCmBgYA0KDQojIENsYXNzaWZpY2F0aW9uDQoNCiMjIERhdGEgQ2xlYW5pbmcNCmBgYHtyfQ0KYnJlYXN0Q2FfUmU8LWJyZWFzdENhICU+JSANCiAgZHJvcF9uYSgpICU+JSANCiAgc2VsZWN0KC1jKDEzOjIyKSkgJT4lIA0KICBzZWxlY3QoLTEpDQoNCmJyZWFzdENhX1JlX25ldzwtYnJlYXN0Q2FfUmUlPiUNCiAgbXV0YXRlKGNvbmNhdmVfcG9pbnRzX21lYW49YGNvbmNhdmUgcG9pbnRzX21lYW5gKSU+JQ0KICBzZWxlY3QoLTEwKQ0KYGBgDQoNCiMjIExBU1NPIGFuZCBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCiMjIyBJbXBsZXRlIExhc3NvIExvZ2lzdGljIFJlZ3Jlc3Npb24gaW4gdGlkeW1vZGVscw0KYGBge3J9DQoNCiMgTWFrZSBzdXJlIHlvdSBzZXQgcmVmZXJlbmNlIGxldmVsICh0byB0aGUgb3V0Y29tZSB5b3UgYXJlIE5PVCBpbnRlcmVzdGVkIGluKQ0KYnJlYXN0Q2FfUmVfbmV3MiA8LSBicmVhc3RDYV9SZV9uZXclPiUNCiAgbXV0YXRlKGRpYWdub3NpcyA9IHJlbGV2ZWwoZmFjdG9yKGRpYWdub3NpcyApLCByZWY9J0InKSkgI3NldCByZWZlcmVuY2UgbGV2ZWwNCg0KZGF0YV9jdjEwIDwtIHZmb2xkX2N2KGJyZWFzdENhX1JlX25ldzIsIHYgPSAxMCkNCg0KDQojIExvZ2lzdGljIExBU1NPIFJlZ3Jlc3Npb24gTW9kZWwgU3BlYw0KbG9naXN0aWNfbGFzc29fc3BlY190dW5lIDwtIGxvZ2lzdGljX3JlZygpICU+JQ0KICAgIHNldF9lbmdpbmUoJ2dsbW5ldCcpICU+JQ0KICAgIHNldF9hcmdzKG1peHR1cmUgPSAxLCBwZW5hbHR5ID0gdHVuZSgpKSAlPiUNCiAgICBzZXRfbW9kZSgnY2xhc3NpZmljYXRpb24nKQ0KDQojIFJlY2lwZQ0KbG9naXN0aWNfcmVjIDwtIHJlY2lwZShkaWFnbm9zaXMgfiAuLCBkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikgJT4lDQogICAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgDQogICAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpDQoNCiMgV29ya2Zsb3cgKFJlY2lwZSArIE1vZGVsKQ0KbG9nX2xhc3NvX3dmIDwtIHdvcmtmbG93KCkgJT4lIA0KICAgIGFkZF9yZWNpcGUobG9naXN0aWNfcmVjKSAlPiUNCiAgICBhZGRfbW9kZWwobG9naXN0aWNfbGFzc29fc3BlY190dW5lKSANCg0KIyBUdW5lIE1vZGVsICh0cnlpbmcgYSB2YXJpZXR5IG9mIHZhbHVlcyBvZiBMYW1iZGEgcGVuYWx0eSkNCnBlbmFsdHlfZ3JpZCA8LSBncmlkX3JlZ3VsYXIoDQogIHBlbmFsdHkocmFuZ2UgPSBjKC01LCAxKSksICNsb2cxMCB0cmFuc2Zvcm1lZCAgKGtlcHQgbW92aW5nIG1pbiBkb3duIGZyb20gMCkNCiAgbGV2ZWxzID0gMTAwKQ0KDQp0dW5lX291dHB1dCA8LSB0dW5lX2dyaWQoIA0KICBsb2dfbGFzc29fd2YsICMgd29ya2Zsb3cNCiAgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCAjIGN2IGZvbGRzDQogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJvY19hdWMsYWNjdXJhY3kpLA0KICBjb250cm9sID0gY29udHJvbF9yZXNhbXBsZXMoc2F2ZV9wcmVkID0gVFJVRSwgZXZlbnRfbGV2ZWwgPSAnc2Vjb25kJyksDQogIGdyaWQgPSBwZW5hbHR5X2dyaWQgIyBwZW5hbHR5IGdyaWQgZGVmaW5lZCBhYm92ZQ0KKQ0KDQojIFZpc3VhbGl6ZSBNb2RlbCBFdmFsdWF0aW9uIE1ldHJpY3MgZnJvbSBUdW5pbmcNCmF1dG9wbG90KHR1bmVfb3V0cHV0KSArIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIyBJbnNwZWN0aW5nIHRoZSBNb2RlbA0KYGBge3J9DQpiZXN0X3NlX3BlbmFsdHkgPC0gc2VsZWN0X2J5X29uZV9zdGRfZXJyKHR1bmVfb3V0cHV0LCBtZXRyaWMgPSAncm9jX2F1YycsIGRlc2MocGVuYWx0eSkpICMgY2hvb3NlIHBlbmFsdHkgdmFsdWUgYmFzZWQgb24gdGhlIGxhcmdlc3QgcGVuYWx0eSB3aXRoaW4gMSBzZSBvZiB0aGUgaGlnaGVzdCBDViByb2NfYXVjDQpmaW5hbF9maXRfc2UgPC0gZmluYWxpemVfd29ya2Zsb3cobG9nX2xhc3NvX3dmLCBiZXN0X3NlX3BlbmFsdHkpICU+JSAjIGluY29ycG9yYXRlcyBwZW5hbHR5IHZhbHVlIHRvIHdvcmtmbG93IA0KICAgIGZpdChkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikNCg0KZmluYWxfZml0X3NlICU+JSB0aWR5KCkNCg0KZmluYWxfZml0X3NlICU+JSB0aWR5KCkgJT4lDQogIGZpbHRlcihlc3RpbWF0ZSA9PSAwKQ0KDQojdmFyaWFibGUgaW1wb3J0YW5jZQ0KZ2xtbmV0X291dHB1dCA8LSBmaW5hbF9maXRfc2UgJT4lIGV4dHJhY3RfZml0X2VuZ2luZSgpDQogICAgDQojIENyZWF0ZSBhIGJvb2xlYW4gbWF0cml4IChwcmVkaWN0b3JzIHggbGFtYmRhcykgb2YgdmFyaWFibGUgZXhjbHVzaW9uDQpib29sX3ByZWRpY3Rvcl9leGNsdWRlIDwtIGdsbW5ldF9vdXRwdXQkYmV0YT09MA0KDQojIExvb3Agb3ZlciBlYWNoIHZhcmlhYmxlDQp2YXJfaW1wIDwtIHNhcHBseShzZXFfbGVuKG5yb3coYm9vbF9wcmVkaWN0b3JfZXhjbHVkZSkpLCBmdW5jdGlvbihyb3cpIHsNCiAgICAjIEV4dHJhY3QgY29lZmZpY2llbnQgcGF0aCAoc29ydGVkIGZyb20gaGlnaGVzdCB0byBsb3dlc3QgbGFtYmRhKQ0KICAgIHRoaXNfY29lZmZfcGF0aCA8LSBib29sX3ByZWRpY3Rvcl9leGNsdWRlW3JvdyxdDQogICAgIyBDb21wdXRlIGFuZCByZXR1cm4gdGhlICMgb2YgbGFtYmRhcyB1bnRpbCB0aGlzIHZhcmlhYmxlIGlzIG91dCBmb3JldmVyDQogICAgbmNvbChib29sX3ByZWRpY3Rvcl9leGNsdWRlKSAtIHdoaWNoLm1pbih0aGlzX2NvZWZmX3BhdGgpICsgMQ0KfSkNCg0KIyBDcmVhdGUgYSBkYXRhc2V0IG9mIHRoaXMgaW5mb3JtYXRpb24gYW5kIHNvcnQNCnZhcl9pbXBfZGF0YSA8LSB0aWJibGUoDQogICAgdmFyX25hbWUgPSByb3duYW1lcyhib29sX3ByZWRpY3Rvcl9leGNsdWRlKSwNCiAgICB2YXJfaW1wID0gdmFyX2ltcA0KKQ0KdmFyX2ltcF9kYXRhICU+JSBhcnJhbmdlKGRlc2ModmFyX2ltcCkpDQpgYGANCg0KIyMjIEV2YWx1YXRpb24gTWV0cmljcw0KYGBge3J9DQojIENWIHJlc3VsdHMgZm9yICJiZXN0IGxhbWJkYSINCnR1bmVfb3V0cHV0ICU+JQ0KICAgIGNvbGxlY3RfbWV0cmljcygpICU+JQ0KICAgIGZpbHRlcihwZW5hbHR5ID09IGJlc3Rfc2VfcGVuYWx0eSAlPiUgcHVsbChwZW5hbHR5KSkNCg0KIyBDb3VudCB1cCBudW1iZXIgb2YgQiBhbmQgTSBpbiB0aGUgdHJhaW5pbmcgZGF0YQ0KYnJlYXN0Q2FfUmVfbmV3MiAlPiUNCiAgICBjb3VudChkaWFnbm9zaXMpICMgTmFtZSBvZiB0aGUgb3V0Y29tZSB2YXJpYWJsZSBnb2VzIGluc2lkZSBjb3VudCgpDQoNCiNDb21wdXRlIHRoZSBOSVINCk5JUjwtIDM1Ny8oMzU3KzIxMikNCk5JUg0KYGBgDQoNCiMjIyBUaHJlc2hvbGQNCmBgYHtyfQ0KIyBTb2Z0IFByZWRpY3Rpb25zIG9uIFRyYWluaW5nIERhdGENCmZpbmFsX291dHB1dCA8LQ0KICBmaW5hbF9maXRfc2UgJT4lIHByZWRpY3QobmV3X2RhdGEgPSBicmVhc3RDYV9SZV9uZXcyLCB0eXBlID0gJ3Byb2InKSAlPiUgICAgIGJpbmRfY29scyhicmVhc3RDYV9SZV9uZXcyKQ0KDQoNCg0KZmluYWxfb3V0cHV0ICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBkaWFnbm9zaXMsIHkgPSAucHJlZF9NKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KDQojIFVzZSBzb2Z0IHByZWRpY3Rpb25zDQpmaW5hbF9vdXRwdXQgJT4lDQogICAgcm9jX2N1cnZlKGRpYWdub3NpcywucHJlZF9NLGV2ZW50X2xldmVsID0gJ3NlY29uZCcpICU+JQ0KICAgIGF1dG9wbG90KCkNCg0KIyB0aHJlc2hvbGRzIGluIHRlcm1zIG9mIHJlZmVyZW5jZSBsZXZlbA0KdGhyZXNob2xkX291dHB1dCA8LSBmaW5hbF9vdXRwdXQgJT4lDQogICAgdGhyZXNob2xkX3BlcmYodHJ1dGggPSBkaWFnbm9zaXMsIGVzdGltYXRlID0gLnByZWRfQiwgdGhyZXNob2xkcyA9IHNlcSgwLDEsYnk9LjAxKSkgDQoNCiMgSi1pbmRleCB2LiB0aHJlc2hvbGQgZm9yIG5vdCBNDQp0aHJlc2hvbGRfb3V0cHV0ICU+JQ0KICAgIGZpbHRlcigubWV0cmljID09ICdqX2luZGV4JykgJT4lDQogICAgZ2dwbG90KGFlcyh4ID0gLnRocmVzaG9sZCwgeSA9IC5lc3RpbWF0ZSkpICsNCiAgICBnZW9tX2xpbmUoKSArDQogICAgbGFicyh5ID0gJ0otaW5kZXgnLCB4ID0gJ3RocmVzaG9sZCcpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkNCg0KdGhyZXNob2xkX291dHB1dCAlPiUNCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnal9pbmRleCcpICU+JQ0KICAgIGFycmFuZ2UoZGVzYyguZXN0aW1hdGUpKQ0KDQojIERpc3RhbmNlIHYuIHRocmVzaG9sZCBmb3Igbm90IE0NCg0KdGhyZXNob2xkX291dHB1dCAlPiUNCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnZGlzdGFuY2UnKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHggPSAudGhyZXNob2xkLCB5ID0gLmVzdGltYXRlKSkgKw0KICAgIGdlb21fbGluZSgpICsNCiAgICBsYWJzKHkgPSAnRGlzdGFuY2UnLCB4ID0gJ3RocmVzaG9sZCcpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkNCg0KdGhyZXNob2xkX291dHB1dCAlPiUNCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnZGlzdGFuY2UnKSAlPiUNCiAgICBhcnJhbmdlKC5lc3RpbWF0ZSkNCg0KbG9nX21ldHJpY3MgPC0gbWV0cmljX3NldChhY2N1cmFjeSxzZW5zLHlhcmRzdGljazo6c3BlYykNCg0KZmluYWxfb3V0cHV0ICU+JQ0KICAgIG11dGF0ZSgucHJlZF9jbGFzcyA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfQiwgbGV2ZWxzKGRpYWdub3NpcyksIHRocmVzaG9sZCA9IC42NCkpICU+JQ0KICAgIGxvZ19tZXRyaWNzKHRydXRoID0gZGlhZ25vc2lzLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzLCBldmVudF9sZXZlbCA9ICdzZWNvbmQnKQ0KYGBgDQoNCiMjIFJhbmRvbSBGb3Jlc3QNCg0KIyMjIEJ1aWxkaW5nIFJhbmRvbSBGb3Jlc3QNCmBgYHtyfQ0KIyBNb2RlbCBTcGVjaWZpY2F0aW9uDQpyZl9zcGVjIDwtIHJhbmRfZm9yZXN0KCkgJT4lDQogIHNldF9lbmdpbmUoZW5naW5lID0gJ3JhbmdlcicpICU+JSANCiAgc2V0X2FyZ3MobXRyeSA9IE5VTEwsICMgc2l6ZSBvZiByYW5kb20gc3Vic2V0IG9mIHZhcmlhYmxlczsgZGVmYXVsdCBpcyBmbG9vcihzcXJ0KG5jb2woeCkpKQ0KICAgICAgICAgICB0cmVlcyA9IDEwMDAsICMgTnVtYmVyIG9mIHRyZWVzDQogICAgICAgICAgIG1pbl9uID0gMiwNCiAgICAgICAgICAgcHJvYmFiaWxpdHkgPSBGQUxTRSwgIyBGQUxTRTogaGFyZCBwcmVkaWN0aW9ucw0KICAgICAgICAgICBpbXBvcnRhbmNlID0gJ2ltcHVyaXR5JykgJT4lIA0KICBzZXRfbW9kZSgnY2xhc3NpZmljYXRpb24nKSAjIGNoYW5nZSB0aGlzIGZvciByZWdyZXNzaW9uIHRyZWUNCg0KIyBSZWNpcGUNCmRhdGFfcmVjIDwtIHJlY2lwZShkaWFnbm9zaXMgfiAuLCBkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikNCg0KIyBXb3JrZmxvd3MNCmRhdGFfd2ZfbXRyeTIgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSAyKSkgJT4lDQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpDQoNCiMgQ3JlYXRlIHdvcmtmbG93cyBmb3IgbXRyeSA9IDQgLCAxMCwgYW5kIDIwDQpkYXRhX3dmX210cnk0IDwtIHdvcmtmbG93KCkgJT4lDQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gNCkpICU+JQ0KICBhZGRfcmVjaXBlKGRhdGFfcmVjKQ0KDQpkYXRhX3dmX210cnkxMCA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfbW9kZWwocmZfc3BlYyAlPiUgc2V0X2FyZ3MobXRyeSA9IDEwKSkgJT4lDQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpDQoNCmRhdGFfd2ZfbXRyeTIwIDwtIHdvcmtmbG93KCkgJT4lDQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gMjApKSAlPiUNCiAgYWRkX3JlY2lwZShkYXRhX3JlYykNCmBgYA0KDQpgYGB7cn0NCiMgRml0IE1vZGVscw0KDQpzZXQuc2VlZCgxMjMpICMgbWFrZSBzdXJlIHRvIHJ1biB0aGlzIGJlZm9yZSBlYWNoIGZpdCBzbyB0aGF0IHlvdSBoYXZlIHRoZSBzYW1lIDEwMDAgdHJlZXMNCmRhdGFfZml0X210cnkyIDwtIGZpdChkYXRhX3dmX210cnkyLCBkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikNCg0Kc2V0LnNlZWQoMTIzKQ0KZGF0YV9maXRfbXRyeTQgPC0gZml0KGRhdGFfd2ZfbXRyeTQsIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKQ0KDQpzZXQuc2VlZCgxMjMpIA0KZGF0YV9maXRfbXRyeTEwIDwtIGZpdChkYXRhX3dmX210cnkxMCwgZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpDQoNCnNldC5zZWVkKDEyMykNCmRhdGFfZml0X210cnkyMCA8LSBmaXQoZGF0YV93Zl9tdHJ5MjAsIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDdXN0b20gRnVuY3Rpb24gdG8gZ2V0IE9PQiBwcmVkaWN0aW9ucywgdHJ1ZSBvYnNlcnZlZCBvdXRjb21lcyBhbmQgYWRkIGEgbW9kZWwgbGFiZWwNCnJmX09PQl9vdXRwdXQgPC0gZnVuY3Rpb24oZml0X21vZGVsLCBtb2RlbF9sYWJlbCwgdHJ1dGgpew0KICAgIHRpYmJsZSgNCiAgICAgICAgICAucHJlZF9kaWFnbm9zaXMgPSBmaXRfbW9kZWwgJT4lIGV4dHJhY3RfZml0X2VuZ2luZSgpICU+JSBwbHVjaygncHJlZGljdGlvbnMnKSwgI09PQiBwcmVkaWN0aW9ucw0KICAgICAgICAgIGRpYWdub3NpcyA9IHRydXRoLA0KICAgICAgICAgIG1vZGVsID0gbW9kZWxfbGFiZWwNCiAgICAgICkNCn0NCg0KI2NoZWNrIG91dCB0aGUgZnVuY3Rpb24gb3V0cHV0DQpyZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnkyLCdtdHJ5MicsIGJyZWFzdENhX1JlX25ldzIgJT4lIHB1bGwoZGlhZ25vc2lzKSkNCmBgYA0KDQpgYGB7cn0NCiMgRXZhbHVhdGUgT09CIE1ldHJpY3MNCg0KZGF0YV9yZl9PT0Jfb3V0cHV0IDwtIGJpbmRfcm93cygNCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnkyLCdtdHJ5MicsIGJyZWFzdENhX1JlX25ldzIgJT4lIHB1bGwoZGlhZ25vc2lzKSksDQogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5NCwnbXRyeTQnLCBicmVhc3RDYV9SZV9uZXcyICU+JSBwdWxsKGRpYWdub3NpcykpLA0KICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTEwLCdtdHJ5MTAnLCBicmVhc3RDYV9SZV9uZXcyICU+JSBwdWxsKGRpYWdub3NpcykpLA0KICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTIwLCdtdHJ5MjAnLCBicmVhc3RDYV9SZV9uZXcyICU+JSBwdWxsKGRpYWdub3NpcykpDQopDQoNCg0KZGF0YV9yZl9PT0Jfb3V0cHV0ICU+JSANCiAgICBncm91cF9ieShtb2RlbCkgJT4lDQogICAgYWNjdXJhY3kodHJ1dGggPSBkaWFnbm9zaXMsIGVzdGltYXRlID0gLnByZWRfZGlhZ25vc2lzKQ0KYGBgDQoNCiMjIyBQcmVsaW1pbmFyeSBpbnRlcnByZXRhdGlvbg0KYGBge3J9DQpkYXRhX3JmX09PQl9vdXRwdXQgJT4lIA0KICAgIGdyb3VwX2J5KG1vZGVsKSAlPiUNCiAgICBhY2N1cmFjeSh0cnV0aCA9IGRpYWdub3NpcywgZXN0aW1hdGUgPS5wcmVkX2RpYWdub3NpcykgJT4lDQogIG11dGF0ZShtdHJ5ID0gYXMubnVtZXJpYyhzdHJpbmdyOjpzdHJfcmVwbGFjZShtb2RlbCwnbXRyeScsJycpKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IG10cnksIHkgPSAuZXN0aW1hdGUgKSkgKyANCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIyMgRXZhbHVhdGluZyB0aGUgZm9yZXN0DQpgYGB7cn0NCmRhdGFfZml0X210cnkyDQpgYGANCg0KYGBge3J9DQpyZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnkyLCdtdHJ5MicsIGJyZWFzdENhX1JlX25ldzIgJT4lIHB1bGwoZGlhZ25vc2lzKSkgJT4lDQogICAgY29uZl9tYXQodHJ1dGggPSBkaWFnbm9zaXMsIGVzdGltYXRlPSAucHJlZF9kaWFnbm9zaXMpDQoNCmBgYA0KDQojIyMgVmFyaWFibGUgaW1wb3J0YW5jZSBtZWFzdXJlcw0KYGBge3J9DQpkYXRhX2ZpdF9tdHJ5MiAlPiUgDQogICAgZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lIA0KICAgIHZpcChudW1fZmVhdHVyZXMgPSAzMCkgKyB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChicmVhc3RDYV9SZV9uZXcyLCBhZXMoeCA9IGRpYWdub3NpcywgeSA9IGFyZWFfd29yc3QpKSArDQogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGJyZWFzdENhX1JlX25ldzIsIGFlcyh4ID0gZGlhZ25vc2lzLCB5ID0gZnJhY3RhbF9kaW1lbnNpb25fbWVhbikpICsNCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KYGBge3J9DQojaW50ZXJtZWRpYXRlIGltcG9ydGFudA0KZ2dwbG90KGJyZWFzdENhX1JlX25ldzIsIGFlcyh4ID0gZGlhZ25vc2lzLCB5ID0gcGVyaW1ldGVyX21lYW4pKSArDQogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMgQ2x1c3RlcmluZw0KDQojIyBEYXRhIENsZWFuaW5nDQpgYGB7cn0NCmJyZWFzdENhX1JlPC1icmVhc3RDYSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIHNlbGVjdCgtYygxMzoyMikpICU+JSANCiAgc2VsZWN0KC0xKQ0KDQpicmVhc3RDYV9SZV9uZXc8LWJyZWFzdENhX1JlJT4lDQogIG11dGF0ZShjb25jYXZlX3BvaW50c19tZWFuPWBjb25jYXZlIHBvaW50c19tZWFuYCklPiUNCiAgc2VsZWN0KC0xMCkgDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoYnJlYXN0Q2FfUmVfbmV3LCBhZXMoeCA9IHBlcmltZXRlcl9tZWFuLCB5ID0gYGNvbmNhdmUgcG9pbnRzX3dvcnN0YCkpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIEstbWVhbnMgY2x1c3RlcmluZyBvbiBwZXJpbWV0ZXJfbWVhbiBhbmQgY29uY2F2ZSBwb2ludHNfd29yc3QNCmBgYHtyfQ0KIyBTZWxlY3QganVzdCB0aGUgcGVyaW1ldGVyX21lYW4gYW5kIGNvbmNhdmUgcG9pbnRzX3dvcnN0IHZhcmlhYmxlcw0KYnJlYXN0Q2FfUmVfbmV3X3N1YiA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgc2VsZWN0KHBlcmltZXRlcl9tZWFuLCBgY29uY2F2ZSBwb2ludHNfd29yc3RgKQ0KDQojIFJ1biBrLW1lYW5zIGZvciBrID0gY2VudGVycyA9IDINCnNldC5zZWVkKDI1MykNCmtjbHVzdF9rMiA8LSBrbWVhbnMoYnJlYXN0Q2FfUmVfbmV3X3N1YiwgY2VudGVycyA9IDIpDQoNCiMgRGlzcGxheSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cw0Ka2NsdXN0X2syJGNsdXN0ZXINCmBgYA0KDQpgYGB7cn0NCiMgQWRkIGEgdmFyaWFibGUgKGtjbHVzdF9rMikgdG8gdGhlIG9yaWdpbmFsIGRhdGFzZXQgDQojIGNvbnRhaW5pbmcgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMNCmJyZWFzdENhX1JlX25ldyA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgbXV0YXRlKGtjbHVzdF8yID0gZmFjdG9yKGtjbHVzdF9rMiRjbHVzdGVyKSkNCmBgYA0KDQpgYGB7cn0NCiMgVmlzdWFsaXplIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIG9uIHRoZSBvcmlnaW5hbCBzY2F0dGVycGxvdA0Kb3JpZ2luYWxDbHVzdGVyUGxvdCA8LSBnZ3Bsb3QoDQogIGJyZWFzdENhX1JlX25ldywNCiAgYWVzKA0KICAgIHggPSBwZXJpbWV0ZXJfbWVhbiwNCiAgICB5ID0gYGNvbmNhdmUgcG9pbnRzX3dvcnN0YCwNCiAgICBjb2xvciA9IGtjbHVzdF8yLA0KICAgIHRleHQgPSBwYXN0ZSgnZGlhZ25vc2lzOiAnLCBkaWFnbm9zaXMpDQogICkNCikgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KZ2dwbG90bHkob3JpZ2luYWxDbHVzdGVyUGxvdCAgLCB0b29sdGlwID0gYyggInRleHQiKSkNCmBgYA0KDQojIyMgQWRkcmVzc2luZyB2YXJpYWJsZSBzY2FsZQ0KDQpgYGB7cn0NCiMgUnVuIGstbWVhbnMgb24gdGhlICpzY2FsZWQqIGRhdGEgKGFsbCB2YXJpYWJsZXMgaGF2ZSBTRCA9IDEpDQpzZXQuc2VlZCgyNTMpDQprY2x1c3RfazJfc2NhbGUgPC0ga21lYW5zKHNjYWxlKGJyZWFzdENhX1JlX25ld19zdWIpLCBjZW50ZXJzID0gMikNCmJyZWFzdENhX1JlX25ldyA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgbXV0YXRlKGtjbHVzdF8yX3NjYWxlID0gZmFjdG9yKGtjbHVzdF9rMl9zY2FsZSRjbHVzdGVyKSkNCg0KIyBWaXN1YWxpemUgdGhlIG5ldyBjbHVzdGVyIGFzc2lnbm1lbnRzDQpzY2FsZWRDbHVzdGVyUGxvdCA8LSBnZ3Bsb3QoDQogIGJyZWFzdENhX1JlX25ldywNCiAgYWVzKA0KICAgIHggPSBwZXJpbWV0ZXJfbWVhbiwNCiAgICB5ID0gYGNvbmNhdmUgcG9pbnRzX3dvcnN0YCwNCiAgICBjb2xvciA9IGtjbHVzdF8yLA0KICAgIHRleHQgPSBwYXN0ZSgnZGlhZ25vc2lzOiAnLCBkaWFnbm9zaXMpDQogICkNCikgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KZ2dwbG90bHkoc2NhbGVkQ2x1c3RlclBsb3QgICwgdG9vbHRpcCA9IGMoICJ0ZXh0IikpDQpgYGANCg0KDQojIyMgQ2x1c3RlcmluZyBvbiBtb3JlIHZhcmlhYmxlcw0KYGBge3J9DQojIFNlbGVjdCB0aGUgdmFyaWFibGVzIHRvIGJlIHVzZWQgaW4gY2x1c3RlcmluZw0KYnJlYXN0Q2FfUmVfbmV3X3N1YjIgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIHNlbGVjdChjKDI6MjEpKQ0KDQojIExvb2sgYXQgc3VtbWFyeSBzdGF0aXN0aWNzIG9mIHRoZSAzIHZhcmlhYmxlcw0Kc3VtbWFyeShicmVhc3RDYV9SZV9uZXdfc3ViMikNCmBgYA0KDQpgYGB7cn0NCnNldC5zZWVkKDI1MykNCmtjbHVzdF9rMl9hbGx2YXJzIDwtIGttZWFucyhzY2FsZShicmVhc3RDYV9SZV9uZXdfc3ViMiksIGNlbnRlcnMgPSAyKQ0KDQpicmVhc3RDYV9SZV9uZXcgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIG11dGF0ZShrY2x1c3RfazJfYWxsdmFycyA9IGZhY3RvcihrY2x1c3RfazJfYWxsdmFycyRjbHVzdGVyKSkNCg0KDQpicmVhc3RDYV9SZV9uZXcgJT4lDQogIGNvdW50KGRpYWdub3NpcyxrY2x1c3RfazJfYWxsdmFycykNCmBgYA0KDQojIyMgSW50ZXJwcmV0aW5nIHRoZSBjbHVzdGVycw0KYGBge3J9DQpicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgZ3JvdXBfYnkoa2NsdXN0X2syX2FsbHZhcnMpICU+JQ0KICAgIHN1bW1hcml6ZShhY3Jvc3MoYygyOjIxKSwgbWVhbikpDQpgYGANCg0KIyMjIFBpY2tpbmcgaw0KYGBge3J9DQojIERhdGEtc3BlY2lmaWMgZnVuY3Rpb24gdG8gY2x1c3RlciBhbmQgY2FsY3VsYXRlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIFNTDQpicmVhc3RDYV9SZV9uZXdfY2x1c3Rlcl9zcyA8LSBmdW5jdGlvbihrKXsNCiAgICAjIFBlcmZvcm0gY2x1c3RlcmluZw0KICAgIGtjbHVzdCA8LSBrbWVhbnMoc2NhbGUoYnJlYXN0Q2FfUmVfbmV3X3N1YjIpLCBjZW50ZXJzID0gaykNCg0KICAgICMgUmV0dXJuIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcw0KICAgIHJldHVybihrY2x1c3QkdG90LndpdGhpbnNzKQ0KfQ0KDQp0aWJibGUoDQogICAgayA9IDE6MjAsDQogICAgdG90X3djX3NzID0gcHVycnI6Om1hcF9kYmwoMToyMCwgYnJlYXN0Q2FfUmVfbmV3X2NsdXN0ZXJfc3MpDQopICU+JSANCiAgICBnZ3Bsb3QoYWVzKHggPSBrLCB5ID0gdG90X3djX3NzKSkgKw0KICAgIGdlb21fcG9pbnQoKSArIA0KICAgIGdlb21fbGluZSgpKw0KICAgIGxhYnMoeCA9ICJOdW1iZXIgb2YgY2x1c3RlcnMiLHkgPSAnVG90YWwgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMnKSArIA0KICAgIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIyBOb3JtYWxpemVkIHZhcmlhYmxlcyBjbHVzdGVyaW5nIHZpc3VhbGl6YWl0b24gb2Ygb3B0aW1hbCBLDQpgYGB7cn0NCiMgUnVuIGstbWVhbnMgZm9yIGsgPSBjZW50ZXJzID0gMw0Kc2V0LnNlZWQoMjUzKQ0Ka2NsdXN0X2szIDwtIGttZWFucyhicmVhc3RDYV9SZV9uZXdfc3ViLCBjZW50ZXJzID0gMykNCg0KIyBEaXNwbGF5IHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzDQprY2x1c3RfazMkY2x1c3Rlcg0KDQojIFJ1biBrLW1lYW5zIG9uIHRoZSAqc2NhbGVkKiBkYXRhIChhbGwgdmFyaWFibGVzIGhhdmUgU0QgPSAxKQ0Kc2V0LnNlZWQoMjUzKQ0Ka2NsdXN0X2szX3NjYWxlIDwtIGttZWFucyhzY2FsZShicmVhc3RDYV9SZV9uZXdfc3ViKSwgY2VudGVycyA9IDMpDQpicmVhc3RDYV9SZV9uZXcgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIG11dGF0ZShrY2x1c3RfM19zY2FsZSA9IGZhY3RvcihrY2x1c3RfazNfc2NhbGUkY2x1c3RlcikpDQoNCiMgVmlzdWFsaXplIHRoZSBuZXcgY2x1c3RlciBhc3NpZ25tZW50cw0KbmV3Q2x1c3RlcktQbG90IDwtIGdncGxvdCgNCiAgYnJlYXN0Q2FfUmVfbmV3LA0KICBhZXMoDQogICAgeCA9IHBlcmltZXRlcl9tZWFuLA0KICAgIHkgPSBgY29uY2F2ZSBwb2ludHNfd29yc3RgLA0KICAgIGNvbG9yID0ga2NsdXN0XzNfc2NhbGUsDQogICAgdGV4dCA9IHBhc3RlKCdkaWFnbm9zaXM6ICcsIGRpYWdub3NpcykNCiAgKQ0KKSArDQogIGdlb21fcG9pbnQoKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQpnZ3Bsb3RseShuZXdDbHVzdGVyS1Bsb3QgICwgdG9vbHRpcCA9IGMoICJ0ZXh0IikpDQpgYGANCg0KIyMjIFBlcmZvcm0gY2x1c3RlcmluZyBvbiBtb3JlIHZhcmlhYmVscyB3aXRoIEs9Mw0KYGBge3J9DQpzZXQuc2VlZCgyNTMpDQprY2x1c3RfazNfYWxsdmFycyA8LSBrbWVhbnMoc2NhbGUoYnJlYXN0Q2FfUmVfbmV3X3N1YjIpLCBjZW50ZXJzID0gMykNCiN3aXRoaW4gY2x1c3RlcnMgc3Ugb2Ygc3F1YXJlcw0Ka2NsdXN0X2szX2FsbHZhcnMNCg0KYnJlYXN0Q2FfUmVfbmV3IDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBtdXRhdGUoa2NsdXN0X2szX2FsbHZhcnMgPSBmYWN0b3Ioa2NsdXN0X2szX2FsbHZhcnMkY2x1c3RlcikpDQoNCg0KYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICBjb3VudChkaWFnbm9zaXMsa2NsdXN0X2szX2FsbHZhcnMpDQpgYGANCg0KIyMjIEludGVycHJldGluZyB0aGUgY2x1c3RlcnMgd2l0aCBrPTMNCmBgYHtyfQ0KYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIGdyb3VwX2J5KGtjbHVzdF9rM19hbGx2YXJzKSAlPiUNCiAgICBzdW1tYXJpemUoYWNyb3NzKGMoMjoyMSksIG1lYW4pKQ0KYGBg